package com.util.thread;

import java.util.concurrent.*;

/**
 * 主线程测试子线程执行耗时
 * <p>
 * while循环进行轮询
 * Thread类的join方法
 * synchronized锁
 * CountDownLatch
 * Future
 * BlockingQueue
 * CyclicBarrier
 *
 * @author xin.ding
 * @date 2020/9/22 9:02
 */

public class ThreadTest {


    public static void main(String[] args) throws Exception {
//        //主线程判断线程isAlived方法进行计算
//        testWhile();
//        //子线程join方法进行计算
//        testJoin();
//        //notify wait方法实现子线程耗时计算
//        testSync();
        //CountDownLatch 等待多个线程结束，CountDownLatch类是一个计数器，可以设置初始线程数（设置后不能改变），
        // 在子线程结束时调用countDown()方法可以使线程数减一，最终为0的时候，调用CountDownLatch的成员方法wait()的线程就会取消BLOKED阻塞状态，进入RUNNABLE从而继续执行
        testCountDownLatch();
//        //Future接口有一个get()方法也可以阻塞当前线程，调用future接口的get方法, 会同步等待该future执行结束, 然后获取到结果
//        testFuture();
//        //BlockingQueue，BlockingQueue的主要的用法是在线程间安全有效的传递数据
//        testBlockQueue();
//        //回环栅栏，通过它可以实现让一组线程等待至某个状态之后再全部同时执行
//        testCyclicBarrier();

    }

    private static void testWhile() throws Exception {
        Thread t = new Thread(() -> {
            //子线程进行字符串连接操作
            int num = 1000;
            String s = "";
            for (int i = 0; i < num; i++) {
                s += "Java";
            }
            System.out.println("t Over");
        });

        //开始计时
        long start = System.currentTimeMillis();
        System.out.println("start = " + start);
        t.start();
        long end = 0;
        //t.getState() != State.TERMINATED这两种判断方式都可以
        while (t.isAlive() == true) {
            end = System.currentTimeMillis();
        }

        System.out.println("end = " + end);
        System.out.println("end - start = " + (end - start));
    }

    private static void testJoin() throws Exception {
        Thread t = new Thread(() -> {
            //子线程进行字符串连接操作
            int num = 1000;
            String s = "";
            for (int i = 0; i < num; i++) {
                s += "Java";
            }
            System.out.println("t Over");
        });

        //开始计时
        long start = System.currentTimeMillis();
        System.out.println("start = " + start);
        t.start();
        //t.join()方法阻塞调用此方法的线程(calling thread)，直到线程t完成，此线程再继续
        t.join();

        long end = System.currentTimeMillis();

        System.out.println("end = " + end);
        System.out.println("end - start = " + (end - start));
    }


    /**
     * 主线程先wait子线程再notify
     * 如果子线程在主线程wati前调用了nitify，会导致主线程无限等待，所以这个思路还是有一定的漏洞的。
     *
     * @throws Exception
     */
    private static void testSync() throws Exception {
        Object lock = new Object();
        Thread t = new Thread(() -> {
            int num = 1000;
            String s = "";
            for (int i = 0; i < num; i++) {
                s += "Java";
            }
            System.out.println("t Over");
            //获取该对象的锁
            synchronized (lock) {
                //子线程唤醒
                lock.notify();
            }
        });
        //计时
        long start = System.currentTimeMillis();
        System.out.println("start = " + start);
        //启动子线程
        t.start();
//        子线程在主线程wati前调用了nitify，会导致主线程无限等待
//        Thread.sleep(1000);
        //调用wait()方法时需要获取该对象的锁
        synchronized (lock) {
            //主线程等待
            lock.wait();
        }

        long end = System.currentTimeMillis();
        System.out.println("end = " + end);
        System.out.println("end - start = " + (end - start));
    }

    /**
     * CountDownLatch类是一个计数器，可以设置初始线程数（设置后不能改变），在子线程结束时调用countDown()方法可以使线程数减一，
     * 最终为0的时候，调用CountDownLatch的成员方法wait()的线程就会取消BLOKED阻塞状态，进入RUNNABLE从而继续执行
     */
    private static void testCountDownLatch() throws Exception {
        int threadNumber = 1;
        final CountDownLatch cdl = new CountDownLatch(threadNumber);//参数为线程个数

        Thread t = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int num = 1000;
            String s = "";
            for (int i = 0; i < num; i++) {
                s += "Java";
            }
            System.out.println("t Over");
            cdl.countDown();//此方法是CountDownLatch的线程数-1
        });

        long start = System.currentTimeMillis();
        System.out.println("start = " + start);
        t.start();
        //线程启动后调用countDownLatch方法
        try {
            //需要捕获异常，当其中线程数为0时这里才会继续运行
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("end = " + end);
        System.out.println("end - start = " + (end - start));
    }

    /**
     * 线程池的submit()的返回对象Future接口有一个get()方法也可以阻塞当前线程（其实该方法主要用途是获取子线程的返回值）
     * 这种方法是得益于Callable接口和Future模式, 调用future接口的get方法, 会同步等待该future执行结束, 然后获取到结果. Callbale接口的接口方法是 V call(); 是可以有返回结果的, 而Runnable的 void run(), 是没有返回结果的.
     * 所以, 这里即使被包装成Callbale接口, future.get返回的结果也是null的.如果需要得到返回结果, 建议使用Callable接口.
     *
     * @throws Exception
     */
    private static void testFuture() throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        Thread t = new Thread(() -> {
            int num = 1000;
            String s = "";
            for (int i = 0; i < num; i++) {
                s += "Java";
            }
            System.out.println("t Over");
        });

        long start = System.currentTimeMillis();
        System.out.println("start = " + start);
        Future future = executorService.submit(t);//子线程启动
        try {
            future.get();//需要捕获两种异常
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("end = " + end);
        System.out.println("end - start = " + (end - start));
        executorService.shutdown();
    }

    /**
     * BlockingQueue的主要的用法是在线程间安全有效的传递数据
     */
    private static void testBlockQueue() {
        BlockingQueue queue = new ArrayBlockingQueue(1);//数组型队列，长度为1
        Thread t = new Thread(() -> {
            int num = 1000;
            String s = "";
            for (int i = 0; i < num; i++) {
                s += "Java";
            }
            System.out.println("t Over");
            try {
                queue.put("OK");//在队列中加入数据
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long start = System.currentTimeMillis();
        System.out.println("start = " + start);
        t.start();
        try {
            //主线程在队列中获取数据，take()方法会阻塞队列，ps还有不会阻塞的方法
            queue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("end = " + end);
        System.out.println("end - start = " + (end - start));
    }

    /**
     * 一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后，CyclicBarrier可以被重用。
     */
    private static void testCyclicBarrier() {
        CyclicBarrier barrier = new CyclicBarrier(2);//参数为线程数
        Thread t = new Thread(() -> {
            int num = 1000;
            String s = "";
            for (int i = 0; i < num; i++) {
                s += "Java";
            }
            System.out.println("t Over");
            try {
                barrier.await();//阻塞
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });
        long start = System.currentTimeMillis();
        System.out.println("start = " + start);
        t.start();
        try {
            //也阻塞,并且当阻塞数量达到指定数目时同时释放
            barrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("end = " + end);
        System.out.println("end - start = " + (end - start));
    }


}
